#include "Sphere.h"
#include "AABB.h"
#include "Matrix33.h"
#include "Matrix44.h"
#include "Plane.h"
#include "Mathutil.h"

//#include <GL/glew.h>

using namespace gti;

Sphere::Sphere()
{
}

Sphere::Sphere(const vector3f& point, float radius) : 
center(point),
radius(radius)
{
}

// get/set members
void Sphere::set(const vector3f& acenter, float aradius)
{
	center = acenter;
	radius = aradius; 
}

// sphere sphere overlap test
CullFlag Sphere::overlap(const Sphere& sphere) const
{
// TO DO: more tests on sphere sphere overlap test
	const vector3f& ba = center - sphere.center;
	const float distanceSquared = ba.squaredLength();	
	const float sum = (radius+sphere.radius);
	const float sum2 = sum*sum;

	if ( distanceSquared > sum2 )
		return OUTSIDE;

	if ( distanceSquared < sum2 )
	{
		/*
		const float l = ba.length();
		if (l+sphere.radius < radius)
			return INSIDE;
		*/
		const float l2 = ba.squaredLength();
		const float tmp = radius*radius-2*radius*sphere.radius+sphere.radius*sphere.radius;
		if (l2 < tmp)
			return INSIDE;

		return OVERLAP;
	}

	return OUTSIDE;
}

// fast sphere sphere overlap test
bool Sphere::collide(const Sphere& sphere) const
{
	const float distanceSquared = (center - sphere.center).squaredLength();
	const float sum = (radius+sphere.radius);
	const float sum2 = sum*sum;

	return ( distanceSquared < sum2 );
}

// sphere triangle overlap test
CullFlag Sphere::overlap(const vector3f& v1,const vector3f& v2,const vector3f& v3) const
{
	Plane plane(v1,v2,v3);

	vector3f closest;
	plane.getClosestPoint(center,closest);

	const float r2 = radius*radius;
	if (!PointInTriangle(closest,v1,v2,v3))
		ClosestPointOnTriangle(v1,v2,v3,closest,closest);

// TODO: inside
	if ( (closest-center).squaredLength() <= r2 )
	{
		return OVERLAP;
	}
	else
	{
		return OUTSIDE;
	}

/*
	// TO DO: optimize = expand closest point on triangle
	// put INSIDE test first
	vector3f r;
	ClosestPointOnTriangle(v1,v2,v3,center,r);

	const float r2 = radius*radius;
	if ( (r-center).squaredLength() <= r2 )
	{
		if (
			(v1-center).squaredLength() < r2 && 
			(v2-center).squaredLength() < r2 && 
			(v3-center).squaredLength() < r2)
			return INSIDE;

		return OVERLAP;
	}

	return OUTSIDE;
*/
/*
// TODO: egb optimize and check
	float r2 = radius*radius;
	vector3f d[3];
	float l[3];
	vector3f a;
	float a2;
	float s;

	d[0] = v1-center;
	l[0] = d[0].squaredLength();
	if (l[0]  < r2)
	{
		d[1] = v2-center;
		l[1] = d[1].squaredLength();
		if (l[1] < r2)
		{
			d[2] = v3-center;
			l[2] = d[2].squaredLength();
			if (l[2] < r2)
			{
				return INSIDE;
			}
			else
			{
				return OVERLAP;
			}
		}
		else
		{
			return OVERLAP;
		}
	}
	else
	{
		d[1] = v2-center;
		l[1] = d[1].squaredLength();
		if (l[1] < r2)
		{
			return OVERLAP;
		}
		else
		{
			d[2] = v3-center;
			l[2] = d[2].squaredLength();
			if (l[2] < r2)
			{
				return OVERLAP;
			}
			else
			{
				// bad luck!!

				s = 1/l[0];
				d[0]*=s;
				a = center - v1;
				s = DotProduct(a,d[0]);
				a2 = DotProduct(a,a);
				
				if ( !(s < 0 && a2 > r2) )
					return OVERLAP;


				s = 1/l[1];
				d[1]*=s;
				a = center - v2;
				s = DotProduct(a,d[1]);
				a2 = DotProduct(a,a);
				
				if ( !(s < 0 && a2 > r2) )
					return OVERLAP;

				s = 1/l[2];
				d[2]*=s;
				a = center - v3;
				s = DotProduct(a,d[2]);
				a2 = DotProduct(a,a);
				
				if ( !(s < 0 && a2 > r2) )
					return OVERLAP;

				return OUTSIDE;
			}
		}

	}
*/
}

void Sphere::render() const
{
#ifdef USE_OPENGL
	GLUquadric *qobj = gluNewQuadric();
	glPushMatrix();
	glTranslatef(center.x,center.y,center.z);
	gluSphere (qobj, radius, 12, 12);
	glPopMatrix();
	gluDeleteQuadric(qobj);
#endif
}

void Sphere::renderWire() const
{
#ifdef USE_OPENGL
	GLUquadric *qobj = gluNewQuadric();
	glPushMatrix();
	glTranslatef(center.x,center.y,center.z);
	gluQuadricDrawStyle(qobj,GLU_LINE);
	gluSphere (qobj, radius, 12, 12);
	glPopMatrix();
	gluDeleteQuadric(qobj);
#endif
#ifdef USE_DIRECT3D

	ID3DXMesh* pMesh = 0;
	HRESULT hr = D3DXCreateSphere(device,
    radius,
    12,
    12,
    &pMesh,
    0);
	assert(hr == D3D_OK);

	DWORD oldFillmode;
	hr = device->GetRenderState(D3DRS_FILLMODE,&oldFillmode);
	assert(hr == D3D_OK);
	hr = device->SetRenderState(D3DRS_FILLMODE,D3DFILL_WIREFRAME);
	assert(hr == D3D_OK);

	DWORD oldCullmode;
	hr = device->GetRenderState( D3DRS_CULLMODE, &oldCullmode);
	assert(hr == D3D_OK);
	hr =device->SetRenderState( D3DRS_CULLMODE, D3DCULL_CCW);
	assert(hr == D3D_OK);

	D3DXMATRIX oldWorld;
	hr = device->GetTransform(D3DTS_WORLD,&oldWorld);
	assert(hr == D3D_OK);
	matrix44 world; world.setTranslationMatrix(center);
	hr = device->SetTransform(D3DTS_WORLD,world);
	assert(hr == D3D_OK);

	hr = pMesh->DrawSubset(0);
	assert(hr == D3D_OK);
	pMesh->Release();

	hr = device->SetTransform(D3DTS_WORLD,&oldWorld);
	assert(hr == D3D_OK);
	hr = device->SetRenderState(D3DRS_FILLMODE,oldFillmode);
	assert(hr == D3D_OK);
	hr = device->SetRenderState( D3DRS_CULLMODE, oldCullmode);
	assert(hr == D3D_OK);

#endif
}

void Sphere::render(const quaternion &q, const vector3f& c, float radius)
{
#ifdef USE_OPENGL
	GLUquadric *qobj = gluNewQuadric();
	glPushMatrix();
	matrix44 m; m.set(q,c);
	glMultMatrixf(m);
	gluQuadricDrawStyle(qobj,GLU_FILL);
	gluSphere (qobj, radius, 24, 24);
	glPopMatrix();
	gluDeleteQuadric(qobj);
#endif
}
